For any suggestions or feedback regarding these notes,
please contact Pragy Agarwal
No!
Different clients have different internet speeds.
If we force all clients to
Solution: we should serve different resolutions to different clients based on their bandwidths!
input
one_piece_1080p_60fps.mp4
output
one_piece_720p_60fps.mp4 one_piece_720p_30fps.mp4
one_piece_480p_60fps.mp4 one_piece_480p_30fps.mp4
one_piece_360p_60fps.mp4 one_piece_360p_30fps.mp4
one_piece_240p_60fps.mp4 one_piece_240p_30fps.mp4
This is called downsampling.
Could you also upsample the video? Nope. (maybe with AI – it will still be a lie)
In the frontend, we can monitor the client’s bandwidth, and serve the highest resolution that will not buffer on their bandwidth.
Maintain a mapping of bandwidth to resolution
Resolutions | Required Bandwidth (H264) |
720p – 1280×720 (HD) | 3Mbps |
1080p – 1920X1080 (FHD) | 6Mbps |
2160p / 4K – 3840×2160 (UHD) | 25Mbps |
Note: actual requirements will depend on the video codec. For example, H265 requires almost ½ the bandwidth compared to H264
Note: you typically don’t want to enforce a resolution on the client — you just detect and configure the “default” resolution, but the client should be able to override this via preferences
The client’s bandwidth can handle 1080p easily — so yes?
No! Screen supports max 720p resolution, so no point in serving a higher resolution to the client, it will just waste their data pack.
In fact, the client’s CPU/GPU will have to work harder to downsample the 1080p video to a 720p screen.
The actual resolution that the client sees should depend not only on the bandwidth, but also on the hardware configuration
Bandwidth: perform a speed test
eg, 100 MB file takes 10 seconds to download, then the bandwidth is 10MBps
Hardware & Software Configuration
console.log(navigator.userAgent)
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36'
Long videos at high resolutions can have very large file size (10s of GBs). If we download the entire video before playback, the client will have to wait for a long time.
Instead, we should “stream” the video — play the video as it is being downloaded.
Suppose a client starts watching a video, but skips to the 30 min mark. How do we handle that?
A 2 hour movie @ 8k resolution @ 60fps, will cost you ~43 TB in the raw format!
Solution: chunk the video (at each resolution) into small sections.
input
one_piece_1080p_60fps.mp4
output
one_piece_720p_60fps_chunk_01.mp4 one_piece_720p_30fps_chunk_01.mp4
one_piece_720p_60fps_chunk_02.mp4 one_piece_720p_30fps_chunk_02.mp4
one_piece_720p_60fps... one_piece_720p_30fps...
one_piece_720p_60fps_chunk_20.mp4 one_piece_720p_30fps_chunk_20.mp4
one_piece_480p_60fps_chunk_01.mp4 one_piece_480p_30fps_chunk_01.mp4
one_piece_480p_60fps_chunk_02.mp4 one_piece_480p_30fps_chunk_02.mp4
one_piece_480p_60fps... one_piece_480p_30fps...
one_piece_480p_60fps_chunk_20.mp4 one_piece_480p_30fps_chunk_20.mp4
one_piece_360p_60fps_chunk_01.mp4 one_piece_360p_30fps_chunk_01.mp4
one_piece_360p_60fps_chunk_02.mp4 one_piece_360p_30fps_chunk_02.mp4
one_piece_360p_60fps... one_piece_360p_30fps.,..
one_piece_360p_60fps_chunk_20.mp4 one_piece_360p_30fps_chunk_20.mp4
Should we chunk by number of bits? 10MB / 100MB … ?
Should we chunk by the time duration? 10 sec / 1 min
Implications of chunk size
Ideal chunk size will depend on the application (more on this later)
We should continuously monitor the client’s bandwidth.
But how? Should we regularly download junk files to measure the bandwidth?
If we do this, won’t we end up wasting precious bandwidth and data?
As the chunks are being downloaded, we can measure the time taken for download. Since each chunk has a known byte size (stored in the file metadata), we can calculate the bandwidth continuously!
Note: each chunk has the same duration (say 10s), but different file sizes (based on how complex the video scene is)
Each next chunk will be downloaded at the appropriate resolution, given the latest bandwidth.
Note that all this calculation & bandwidth monitoring is happening in the client side.
async getChunk(url: string): Promise<{data: ByteArray[], size: integer, timeMs: integer}> {
startTime = new Date()
response = await get(url => …)
timeMs = new Date() - startTime()
}
No. Most people don’t watch the entire video – they skip ahead, or switch to another video.
We should download the next chunk “Just in Time” — download the next chunk when
remaining-playback-time <= (time-to-download-next-chunk + x% buffer )
By the time the user reaches the end of available playback, the next chunk should be available — so that we don’t waste any bandwidth, and the client doesn’t need to buffer
https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8
#EXTM3U
#EXT-X-VERSION:1
## Created with Unified Streaming Platform (version=1.13.0-29687)
# variants
#EXT-X-STREAM-INF:BANDWIDTH=493000,CODECS="mp4a.40.2,avc1.66.30",RESOLUTION=224x100,FRAME-RATE=24
tears-of-steel-audio_eng=64008-video_eng=401000.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=932000,CODECS="mp4a.40.2,avc1.66.30",RESOLUTION=448x200,FRAME-RATE=24
tears-of-steel-audio_eng=128002-video_eng=751000.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1197000,CODECS="mp4a.40.2,avc1.77.31",RESOLUTION=784x350,FRAME-RATE=24
tears-of-steel-audio_eng=128002-video_eng=1001000.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1727000,CODECS="mp4a.40.2,avc1.100.40",RESOLUTION=1680x750,FRAME-RATE=24,VIDEO-RANGE=SDR
tears-of-steel-audio_eng=128002-video_eng=1501000.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2468000,CODECS="mp4a.40.2,avc1.100.40",RESOLUTION=1680x750,FRAME-RATE=24,VIDEO-RANGE=SDR
tears-of-steel-audio_eng=128002-video_eng=2200000.m3u8
# variants
#EXT-X-STREAM-INF:BANDWIDTH=68000,CODECS="mp4a.40.2"
tears-of-steel-audio_eng=64008.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=136000,CODECS="mp4a.40.2"
tears-of-steel-audio_eng=128002.m3u8
https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/tears-of-steel-audio_eng=128002-video_eng=2200000.m3u8
#EXTM3U
#EXT-X-VERSION:1
## Created with Unified Streaming Platform (version=1.13.0-29687)
#EXT-X-MEDIA-SEQUENCE:1
#EXT-X-TARGETDURATION:4
#USP-X-TIMESTAMP-MAP:MPEGTS=900000,LOCAL=1970-01-01T00:00:00Z
#EXTINF:4, no desc
tears-of-steel-audio_eng=128002-video_eng=2200000-1.ts
#EXTINF:4, no desc
tears-of-steel-audio_eng=128002-video_eng=2200000-2.ts
#EXTINF:4, no desc
tears-of-steel-audio_eng=128002-video_eng=2200000-3.ts
#EXTINF:4, no desc
tears-of-steel-audio_eng=128002-video_eng=2200000-4.ts
...
#EXTINF:4, no desc
tears-of-steel-audio_eng=128002-video_eng=2200000-183.ts
#EXTINF:2, no desc
tears-of-steel-audio_eng=128002-video_eng=2200000-184.ts
#EXT-X-ENDLIST
https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/tears-of-steel-audio_eng=128002-video_eng=2200000-1.ts
(binary file – can be played in your video player)
Youtube / Netflix / JioCinema
The video is uploaded at the upload microservice, which stores in the database.
Apart from inserting the video into S3 & metadata into SQL, the upload service will also insert a task in a Message Queue.
Downsampling service picks tasks from the message queue. For each task,
input
one_piece_1080p_60fps.mp4
output
one_piece_720p_60fps_chunk_01.mp4 one_piece_720p_30fps_chunk_01.mp4
one_piece_720p_60fps_chunk_01.mp4 one_piece_720p_30fps_chunk_01.mp4
one_piece_720p_60fps... one_piece_720p_30fps...
one_piece_720p_60fps_chunk_20.mp4 one_piece_720p_30fps_chunk_20.mp4
one_piece_480p_60fps_chunk_01.mp4 one_piece_480p_30fps_chunk_01.mp4
one_piece_480p_60fps_chunk_01.mp4 one_piece_480p_30fps_chunk_01.mp4
one_piece_480p_60fps... one_piece_480p_30fps...
one_piece_480p_60fps_chunk_20.mp4 one_piece_480p_30fps_chunk_20.mp4
one_piece_360p_60fps_chunk_01.mp4 one_piece_360p_30fps_chunk_01.mp4
one_piece_360p_60fps_chunk_01.mp4 one_piece_360p_30fps_chunk_01.mp4
one_piece_360p_60fps... one_piece_360p_30fps...
one_piece_360p_60fps_chunk_20.mp4 one_piece_360p_30fps_chunk_20.mp4
We need to serve both the video chunks (.ts files) & the metadata (.m3u8 file)
No! File Storage Services like S3 are not optimized for fast reads/writes at high scale.
Instead the user will download the chunks from the CDN (always).
“ CDNs perform the heavy lifting for serving videos @ scale ”
If the metadata (m3u8) file is static, it can also be served from the CDN.
However, it can be dynamic as well
In such cases, we have to “construct” the m3u8 for each request via a backend app server.
The app server can depend on a caching layer to store the constructed m3u8, instead of fetching the metadata from the SQL db & reconstructing the file every time.
For pre-recorded content, the chunk sizes can be larger — typically kept between 5s to 10s to reduce the number of network calls.
“ Apple recommends 6s chunk size for HLS ”
In some services, the chunk size can be as large as 1 minute
Scaler Recorded Video (not live class) have a chunk size of 16.5s